﻿// Copyright (c)  MOKEYISH. All rights reserved.
// Licensed under the MIT License, See License.txt in the project root for license information.

using System;
using System.Diagnostics;
using System.Linq;
using NSQL.Expressions;
using NSQL.Expressions.Functions;
using NSQL.Parser.Grammars;
using NSQL.Statements;
using NSQL.Statements.SelectStatements;
using GroupOperator = NSQL.Expressions.GroupOperator;
using SelectStatement = NSQL.Statements.SelectStatements.SelectStatement;
using UnaryOperator = NSQL.Expressions.UnaryOperator;

namespace NSQL.Parser
{
    [CLSCompliant(false)]
    public class NSqlGrammarVisitor : NSqlParserBaseVisitor<ISqlObject>
    {
        public override ISqlObject VisitRelational_expression(NSqlParser.Relational_expressionContext context)
        {
            var relationalOperator = context.relational_operator();
            var children = context.relational_expression();
            if (relationalOperator == null || children.Length != 2) return base.VisitRelational_expression(context);
            var left = (IExpression) Visit(children[0]);
            var right = (IExpression) Visit(children[1]);
            var op = ComparisonExpression.ParseOperator(relationalOperator.GetText());
            return new ComparisonExpression(left, right, op);
        }

        public override ISqlObject VisitNumeric(NSqlParser.NumericContext context)
        {
            var m = context.UNSIGNED_INTEGER();
            var num = context.GetText();
            if (num.Contains('.'))
            {
                return (ConstantExpression) double.Parse(num);
            }

            return (ConstantExpression) int.Parse(num);
        }

        public override ISqlObject VisitRegular_id(NSqlParser.Regular_idContext context)
        {
            var nonReservedKeywords = context.non_reserved_keywords_pre12c();
            if (nonReservedKeywords != null)
            {
                var keyword = nonReservedKeywords.GetText().ToLower();

                switch (keyword)
                {
                    case "sysdate":
                    case "now":
                        return Static.Now();
                }
            }

            return (ColumnExpression) context.GetText();
        }

        public override ISqlObject VisitId_expression(NSqlParser.Id_expressionContext context)
        {
            if (context.special_id() != null || context.special_property() != null)
            {
                return VisitChildren(context);
            }

            var delimitedId = context.DELIMITED_ID();
            if (delimitedId != null)
            {
                var text = delimitedId.GetText();
                return new ColumnExpression(text.Substring(1, text.Length - 2), true);
            }

            return VisitChildren(context);
        }

        public override ISqlObject VisitCompound_expression(NSqlParser.Compound_expressionContext context)
        {
            var inOp = context.IN();
            if (inOp != null)
            {
                var leftExpression = (IExpression) VisitConcatenation(context.concatenation(0));
                var inElements = (IExpressions) VisitIn_elements(context.in_elements());
                if (context.NOT() != null)
                {
                    return new NotInExpression(leftExpression, inElements);
                }

                return new InExpression(leftExpression, inElements);
            }

            var betweenOp = context.BETWEEN();

            if (betweenOp != null)
            {
                var left = (IExpression) VisitConcatenation(context.concatenation(0));
                var begin = (INumericExpression) VisitConcatenation(context.between_elements().concatenation(0));
                var end = (INumericExpression) VisitConcatenation(context.between_elements().concatenation(1));
                return new BetweenExpression(left, begin, end);
            }

            var likeOp = context.LIKE();
            if (likeOp != null)
            {
                var left = (IExpression) VisitConcatenation(context.concatenation(0));
                var pattern = (StringExpression) VisitConcatenation(context.concatenation(1));
                return new LikeExpression(left, pattern);
            }

            return base.VisitCompound_expression(context);
        }

        public override ISqlObject VisitIn_elements(NSqlParser.In_elementsContext context)
        {
            var concatenation = context.concatenation();
            if (concatenation != null)
            {
                return new ExpressionCollection(context.concatenation().Select(VisitConcatenation).Cast<IExpression>());
            }

            return base.VisitIn_elements(context);
        }

        public override ISqlObject VisitQuoted_string(NSqlParser.Quoted_stringContext context)
        {
            if (context == null) return null;
            var value = context.GetText();
            return (StringExpression) value.Substring(1, value.Length - 2);
        }

        public override ISqlObject VisitLogical_expression(NSqlParser.Logical_expressionContext context)
        {
            if (context.AND() != null)
            {
                var contexts = context.logical_expression();
                Debug.Assert(contexts.Length == 2);
                var expresions = contexts.Select(VisitLogical_expression).Cast<ILogicalExpression>().ToList();
                return new GroupExpression(expresions[0], expresions[1], GroupOperator.And);
            }

            if (context.OR() != null)
            {
                var contexts = context.logical_expression();
                Debug.Assert(contexts.Length == 2);
                var expresions = contexts.Select(VisitLogical_expression).Cast<ILogicalExpression>().ToList();
                return new GroupExpression(expresions[0], expresions[1], GroupOperator.Or);
            }


            if (context.IS()?.Length == 1 && context.NULL()?.Length == 1)
            {
                var multisetContext = context.multiset_expression();
                var expression = (IExpression) VisitMultiset_expression(multisetContext);
                if (context.NOT()?.Length == 1)
                {
                    return new NotNullExpression(expression);
                }

                return new NullExpression(expression);
            }

            // ReSharper disable once InvertIf
            if (context.NOT()?.Length == 1)
            {
                var contexts = context.logical_expression();
                Debug.Assert(contexts.Length == 1);
                var expression = (ILogicalExpression) VisitLogical_expression(contexts[0]);

                return new NotExpression(expression);
            }


            return base.VisitLogical_expression(context);
        }

        protected override ISqlObject AggregateResult(ISqlObject aggregate, ISqlObject nextResult)
        {
            return nextResult ?? aggregate;
        }

        public override ISqlObject VisitSpecial_id(NSqlParser.Special_idContext context)
        {
            var numeric = context.numeric().GetText();
            return (ColumnExpression) int.Parse(numeric);
        }

        public override ISqlObject VisitConstant(NSqlParser.ConstantContext context)
        {
            if (context.DATE_TIME() != null)
            {
                var text = context.GetText();
                return (DateTimeExpression) DateTime.Parse(text.Substring(1, text.Length - 2));
            }

            if (context.TRUE() != null)
            {
                return BooleanExpression.True;
            }
            
            if (context.FALSE() != null)
            {
                return BooleanExpression.False;
            }

            if (context.INTERVAL() != null)
            {
                if (context.SECOND().Length == 1)
                {
                    return IntervalExpression.FromSeconds(long.Parse(context.quoted_string()[0].GetText().Trim('\'')));
                }
                if (context.MINUTE().Length == 1)
                {
                    return IntervalExpression.FromMinutes(long.Parse(context.quoted_string()[0].GetText().Trim('\'')));
                }
                if (context.HOUR().Length == 1)
                {
                    return IntervalExpression.FromHours(long.Parse(context.quoted_string()[0].GetText().Trim('\'')));
                }
                if (context.DAY().Length == 1)
                {
                    return IntervalExpression.FromDays(long.Parse(context.quoted_string()[0].GetText().Trim('\'')));
                }
                if (context.MONTH() != null)
                {
                    return IntervalExpression.FromMonths(long.Parse(context.quoted_string()[0].GetText().Trim('\'')));
                }
                if (context.YEAR() != null)
                {
                    return IntervalExpression.FromYears(long.Parse(context.quoted_string()[0].GetText().Trim('\'')));
                }
                
            }
            return base.VisitConstant(context);
        }

        public override ISqlObject VisitNumeric_function(NSqlParser.Numeric_functionContext context)
        {
            if (context.COUNT() != null)
            {
                var expression = (IExpression) VisitConcatenation(context.concatenation());
                if (context.DISTINCT() != null)
                {
                    return new CountFunction(expression, AggregateType.Distinct);
                }

                if (context.ALL() != null)
                {
                    return new CountFunction(expression, AggregateType.All);
                }

                return new CountFunction(expression, AggregateType.None);
            }

            if (context.SUM() != null)
            {
                var expression = (IValueExpression) VisitExpression(context.expression());
                if (context.DISTINCT() != null)
                {
                    return new SumFunction(expression, AggregateType.Distinct);
                }

                if (context.ALL() != null)
                {
                    return new SumFunction(expression, AggregateType.All);
                }
                return new SumFunction(expression, AggregateType.None);
            }

            if (context.ROUND() != null)
            {
                var expression = (IValueExpression) VisitExpression(context.expression());
                var unsignedInteger = context.UNSIGNED_INTEGER()?.GetText();
                if (int.TryParse(unsignedInteger, out var decimals))
                {

                    return new RoundFunction(expression, (IntegerExpression) decimals);
                }
                return new RoundFunction(expression, null);
            }

            if (context.MAX() != null)
            {
                var expression = (IExpression) VisitExpression(context.expression());
                if (context.DISTINCT() != null)
                {
                    return new MaxFunction(expression, AggregateType.Distinct);
                }

                if (context.ALL() != null)
                {
                    return new MaxFunction(expression, AggregateType.All);
                }
                return new MaxFunction(expression, AggregateType.None);
            }


            if (context.AVG() != null)
            {
                var expression = (IExpression) VisitExpression(context.expression());
                if (context.DISTINCT() != null)
                {
                    return new AvgFunction(expression, AggregateType.Distinct);
                }

                if (context.ALL() != null)
                {
                    return new AvgFunction(expression, AggregateType.All);
                }
                return new AvgFunction(expression, AggregateType.None);
            }

            if (context.MIN() != null)
            {
                var expression = (IExpression) VisitExpression(context.expression());
                if (context.DISTINCT() != null)
                {
                    return new MinFunction(expression, AggregateType.Distinct);
                }

                if (context.ALL() != null)
                {
                    return new MinFunction(expression, AggregateType.All);
                }
                return new MinFunction(expression, AggregateType.None);
            }

            return base.VisitNumeric_function(context);
        }

        public override ISqlObject VisitSearched_case_statement(
            NSqlParser.Searched_case_statementContext context)
        {
            var matched = context.searched_case_when_part().Select(o =>
            {
                var parts = o.expression();
                Debug.Assert(parts.Length == 2);
                return new MatchPair<ILogicalExpression, IValueExpression>()
                {
                    When = (ILogicalExpression) VisitExpression(parts[0]),
                    Then = (IValueExpression) VisitExpression(parts[1])
                };
            });

            var defaultValue = (IValueExpression) VisitExpression(context.case_else_part().expression());
            return new MatchExpression(matched, defaultValue);
        }

        public override ISqlObject VisitSimple_case_statement(NSqlParser.Simple_case_statementContext context)
        {
            var value = (IValueExpression) VisitExpression(context.expression());
            var matched = context.simple_case_when_part().Select(o =>
            {
                var parts = o.expression();
                Debug.Assert(parts.Length == 2);
                return new MatchPair<IValueExpression, IValueExpression>()
                {
                    When = (IValueExpression) VisitExpression(parts[0]),
                    Then = (IValueExpression) VisitExpression(parts[1])
                };
            });

            var defaultValue = (IValueExpression) VisitExpression(context.case_else_part().expression());
            return new SimpleMatchExpression(value, matched, defaultValue);
        }

        public override ISqlObject VisitSelect_statement(NSqlParser.Select_statementContext context)
        {
            var statement = VisitSubquery(context.subquery());
            if (statement is ISelectStatement selectStatement)
            {
                var orderByClauseContexts = context.order_by_clause();
                Debug.Assert(orderByClauseContexts.Length == 0 || orderByClauseContexts.Length == 1);
                if (orderByClauseContexts.Length == 1)
                {
                    selectStatement.Order.AddRange(context.order_by_clause()[0].order_by_elements()
                        .Select(VisitOrder_by_elements).Cast<ISortingElement>());
                }

                var offsetClause = context.offset_clause().FirstOrDefault();

                if (offsetClause != null)
                {
                    if (offsetClause.SKIP_() != null)
                    {
                        selectStatement.Skip = int.Parse(offsetClause.numeric().GetText());
                    }
                    else if (offsetClause.OFFSET() != null && (offsetClause.ROW() != null || offsetClause.ROWS() != null))
                    {
                        // oracle

                        selectStatement.Skip = int.Parse(offsetClause.expression().GetText());
                    }
                }

                var fetchClause = context.fetch_clause().FirstOrDefault();
                if (fetchClause != null)
                {
                    if (fetchClause.LIMIT() != null)
                    {
                        selectStatement.Limit = int.Parse(fetchClause.numeric().GetText());
                    }
                    else if (fetchClause.FETCH() != null && (fetchClause.ROW() != null || fetchClause.ROWS() != null))
                    {
                        // oracle
                        if (fetchClause.FIRST() != null)
                        {
                            selectStatement.Limit = int.Parse(fetchClause.expression().GetText());
                        }
                        else
                        {
                            throw new NotSupportedException();
                        }
                    }
                }

                return selectStatement;
            }

            return statement;
        }

        public override ISqlObject VisitQuery_block(NSqlParser.Query_blockContext context)
        {
            Debug.Assert(context.SELECT() != null);
            var selectStatement = new SelectStatement();
            if (context.ASTERISK() != null)
            {
                // *
                selectStatement.Elements.Add(SelectElement.Asterisk);
            }

            selectStatement.Elements.AddRange(context.selected_element()
                .Select(VisitSelected_element).Cast<ISelectElement>());


            // from 
            var fromClause = context.from_clause();
            if (fromClause != null)
            {
                selectStatement.TableSources.AddRange(context.from_clause().table_ref_list().table_ref()
                    .Select(VisitTable_ref).Cast<ITableSource>());
            }

            // where 
            var whereClauseContext = context.where_clause();
            if (whereClauseContext != null)
            {
                selectStatement.Condition = (ILogicalExpression) VisitWhere_clause(whereClauseContext);
            }

            // group

            // having
            return selectStatement;
        }

        public override ISqlObject VisitSelected_element(NSqlParser.Selected_elementContext context)
        {
            var element = (IExpression) VisitSelect_list_elements(context.select_list_elements());
            var alias = context.column_alias()?.GetText();
            return new SelectElement(element, alias);
        }

        public override ISqlObject VisitWhere_clause(NSqlParser.Where_clauseContext context)
        {
            return VisitLogical_expression(context.expression().logical_expression());
        }

        public override ISqlObject VisitOrder_by_elements(NSqlParser.Order_by_elementsContext context)
        {
            var expression = (IExpression) VisitExpression(context.expression());
            if (context.ASC() != null)
            {
                return new SortingElement(expression, SortingDirection.Ascending);
            }

            if (context.DESC() != null)
            {
                return new SortingElement(expression, SortingDirection.Descending);
            }

            return new SortingElement(expression);
        }

        public override ISqlObject VisitTable_ref(NSqlParser.Table_refContext context)
        {
            var tableSource = (ITableSource) VisitTable_ref_aux(context.table_ref_aux());

            var t = tableSource;
            foreach (var joinClauseContext in context.join_clause())
            {
                var condition =
                    (ILogicalExpression) VisitExpression(joinClauseContext.join_on_part()[0].condition().expression());

                var joinTable = (ITableSource) VisitTable_ref_aux(joinClauseContext.table_ref_aux());

                var outerJoinType = joinClauseContext.outer_join_type();
                if (outerJoinType != null)
                {
                    if (outerJoinType.LEFT() != null)
                    {
                        t = t.LeftJoin(joinTable).On(condition);
                    }
                    else if (outerJoinType.RIGHT() != null)
                    {
                        t = t.RightJoin(joinTable).On(condition);
                    }
                }
                else
                {
                    t = t.InnerJoin(joinTable).On(condition);
                }
            }

            return tableSource;
        }

        public override ISqlObject VisitTable_ref_aux(NSqlParser.Table_ref_auxContext context)
        {
            var alias = context.table_alias()?.GetText();
            var tableSource = (ITableSource)
                VisitTable_ref_aux_internal_one(
                    (NSqlParser.Table_ref_aux_internal_oneContext) context.table_ref_aux_internal());
            tableSource.Alias = alias;
            return tableSource;
        }

        public override ISqlObject VisitTableview_name(NSqlParser.Tableview_nameContext context)
        {
            var s1 = context.identifier()?.GetText();
            var s2 = context.id_expression()?.GetText();
            return s2 != null ? new TableSource(s2, s1) : new TableSource(s1);
        }

        public override ISqlObject VisitConcatenation(NSqlParser.ConcatenationContext context)
        {
            if (context.PLUS_SIGN() != null)
            {
                var concatenationContexts = context.concatenation();
                Debug.Assert(concatenationContexts.Length == 2);
                
                var left =  VisitConcatenation(concatenationContexts[0]);
                var right =  VisitConcatenation(concatenationContexts[1]);
                if (right is IIntervalExpression intervalExpression)
                {
                    return new AddIntervalFunction((IDateTimeExpression)left, intervalExpression);
                }
                return new ArithmeticExpression((INumericExpression)left, (INumericExpression)right, ArithmeticOperator.Plus);
            }

            if (context.MINUS_SIGN() != null)
            {
                var concatenationContexts = context.concatenation();
                Debug.Assert(concatenationContexts.Length == 2);
                var left = (INumericExpression) VisitConcatenation(concatenationContexts[0]);
                var right = (INumericExpression) VisitConcatenation(concatenationContexts[1]);
                return new ArithmeticExpression(left, right, ArithmeticOperator.Minus);
            }

            if (context.ASTERISK() != null)
            {
                var concatenationContexts = context.concatenation();
                Debug.Assert(concatenationContexts.Length == 2);
                var left = (INumericExpression) VisitConcatenation(concatenationContexts[0]);
                var right = (INumericExpression) VisitConcatenation(concatenationContexts[1]);
                return new ArithmeticExpression(left, right, ArithmeticOperator.Multiply);
            }

            if (context.SOLIDUS() != null)
            {
                var concatenationContexts = context.concatenation();
                Debug.Assert(concatenationContexts.Length == 2);
                var left = (INumericExpression) VisitConcatenation(concatenationContexts[0]);
                var right = (INumericExpression) VisitConcatenation(concatenationContexts[1]);
                return new ArithmeticExpression(left, right, ArithmeticOperator.Divide);
            }

            return base.VisitConcatenation(context);
        }

        public override ISqlObject VisitUnary_expression(NSqlParser.Unary_expressionContext context)
        {
            if (context.PLUS_SIGN() != null)
            {
                var expression = (IValueExpression) VisitUnary_expression(context.unary_expression());
                return new UnaryExpression(expression, UnaryOperator.Plus);
            }

            if (context.MINUS_SIGN() != null)
            {
                var expression = (IValueExpression) VisitUnary_expression(context.unary_expression());
                if (expression is IConstantNumericExpression constantNumeric)
                {
                    constantNumeric.MakeOpposite();
                    return constantNumeric;
                }
                return new UnaryExpression(expression, UnaryOperator.Minus);
            }

            return base.VisitUnary_expression(context);
        }

        public override ISqlObject VisitString_function(NSqlParser.String_functionContext context)
        {
            if (context.TO_CHAR() != null)
            {
                var quotedStringContexts = context.quoted_string();
                Debug.Assert(quotedStringContexts.Length == 1);
                IValueExpression exp;
                if (context.table_element() != null)
                {
                    exp = (IValueExpression) VisitTable_element(context.table_element());
                }
                else
                {
                    exp = (IValueExpression) VisitExpression(context.expression()[0]);
                }
                var fmt = (IStringExpression) VisitQuoted_string(quotedStringContexts[0]);
                return new ToCharFunction(exp, fmt);
            }

            if (context.TRIM() != null)
            {
                var concatenation = (IValueExpression) VisitConcatenation(context.concatenation());

                var trim = (IConstantStringExpression) VisitQuoted_string(context.quoted_string().FirstOrDefault());
                if (context.LEADING() != null)
                {
                    return new TrimFunction(concatenation, trim, TrimType.Leading);
                }

                if (context.TRAILING() != null)
                {
                    
                    return new TrimFunction(concatenation, trim, TrimType.Trailing);
                }
                if (context.BOTH() != null)
                {
                    
                    return new TrimFunction(concatenation, trim);
                }
                return new TrimFunction(concatenation);
            }

            if (context.SUBSTR() != null)
            {
                var expressionContexts = context.expression();
                var text = (IValueExpression) VisitExpression(expressionContexts[0]);
                var position = (IntegerExpression) VisitExpression(expressionContexts[1]);
                IIntegerExpression length = null;
                if (expressionContexts.Length == 3)
                {
                    length = (IntegerExpression) VisitExpression(expressionContexts[2]);
                }

                return new SubStringFunction(text, position, length);
            }

            if (context.LEFT() != null)
            {
                var expressionContexts = context.expression();
                var text = (IStringExpression) VisitExpression(expressionContexts[0]);
                var position = (IIntegerExpression) VisitExpression(expressionContexts[1]);
                return new LeftFunction(text, position);
            }

            if (context.RIGHT() != null)
            {
                var expressionContexts = context.expression();
                var text = (IStringExpression) VisitExpression(expressionContexts[0]);
                var position = (IIntegerExpression) VisitExpression(expressionContexts[1]);
                return new RightFunction(text, position);
            }
            
            

            if (context.NVL() != null)
            {
                var expressionContexts = context.expression();
                Debug.Assert(expressionContexts.Length >= 2);
                var exp = (IValueExpression)VisitExpression(expressionContexts[0]);

                if (expressionContexts.Length == 2)
                {
                    var @default= (IValueExpression) VisitExpression(expressionContexts[1]);
                    return new NvlFunction(exp, @default);
                }
                else
                {
                    var @default= (IValueExpression) VisitExpression(expressionContexts[2]);
                    var then= (IValueExpression) VisitExpression(expressionContexts[1]);
                    return new NvlFunction(exp, then, @default);
                }
            }
            return base.VisitString_function(context);
        }

        public override ISqlObject VisitGeneral_element_part(NSqlParser.General_element_partContext context)
        {
            var idExpressionContexts = context.id_expression();
            if (idExpressionContexts.Length == 1)
            {
                var argumentContexts = context.function_argument()?.argument();
                if (argumentContexts?.Length > 0)
                {
                    switch (idExpressionContexts[0].GetText().ToLower())
                    {
                        case "trim":
                            if (argumentContexts.Length == 1)
                            {
                                return new TrimFunction((IValueExpression) VisitArgument(argumentContexts[0]));
                            }

                            if (argumentContexts.Length == 2)
                            {
                                return new TrimFunction((IValueExpression) VisitArgument(argumentContexts[0]),
                                    (IConstantStringExpression) VisitArgument(argumentContexts[1]));
                            }

                            break;
                        case "ltrim":
                        case "trimstart":
                        case "trim_start":
                            if (argumentContexts.Length == 1)
                            {
                                return new TrimFunction((IValueExpression) VisitArgument(argumentContexts[0]),
                                    TrimType.Leading);
                            }

                            if (argumentContexts.Length == 2)
                            {
                                return new TrimFunction((IValueExpression) VisitArgument(argumentContexts[0]),
                                    (IConstantStringExpression) VisitArgument(argumentContexts[1]), TrimType.Leading);
                            }

                            break;
                        case "rtrim":
                        case "trimend":
                        case "trim_end":
                            if (argumentContexts.Length == 1)
                            {
                                return new TrimFunction((IValueExpression) VisitArgument(argumentContexts[0]),
                                    TrimType.Trailing);
                            }

                            if (argumentContexts.Length == 2)
                            {
                                return new TrimFunction((IValueExpression) VisitArgument(argumentContexts[0]),
                                    (IConstantStringExpression) VisitArgument(argumentContexts[1]),
                                    TrimType.Trailing);
                            }

                            break;
                        case "lpad":
                        case "padleft":
                        case "pad_left":
                            if (argumentContexts.Length == 2)
                            {
                                var text = (IValueExpression) VisitArgument(argumentContexts[0]);
                                var length = (IConstantIntegerExpression) VisitArgument(argumentContexts[1]);
                                return new PadFunction(text, PaddingType.Left, length);
                            }

                            if (argumentContexts.Length == 3)
                            {
                                var text = (IValueExpression) VisitArgument(argumentContexts[0]);
                                var length = (IConstantIntegerExpression) VisitArgument(argumentContexts[1]);
                                var pading = (IConstantStringExpression) VisitArgument(argumentContexts[2]);
                                return new PadFunction(text, PaddingType.Left, length, pading);

                            }
                            break;
                        case "rpad":
                        case "padright":
                        case "pad_right":
                            if (argumentContexts.Length == 2)
                            {
                                var text = (IValueExpression) VisitArgument(argumentContexts[0]);
                                var length = (IConstantIntegerExpression) VisitArgument(argumentContexts[1]);
                                return new PadFunction(text, PaddingType.Right, length);
                            }

                            if (argumentContexts.Length == 3)
                            {
                                var text = (IValueExpression) VisitArgument(argumentContexts[0]);
                                var length = (IConstantIntegerExpression) VisitArgument(argumentContexts[1]);
                                var pading = (IConstantStringExpression) VisitArgument(argumentContexts[2]);
                                return new PadFunction(text, PaddingType.Right, length, pading);

                            }
                            break;
                        case "length":

                            if (argumentContexts.Length == 1)
                            {
                                return new LengthFunction((IValueExpression) VisitArgument(argumentContexts[0]));
                            }

                            break;
                        
                        case "sin":
                            if (argumentContexts.Length == 1)
                            {
                                return new SinFunction((IValueExpression) VisitArgument(argumentContexts[0]));
                            }
                            break;
                        case "cos":
                            if (argumentContexts.Length == 1)
                            {
                                return new CosFunction((IValueExpression) VisitArgument(argumentContexts[0]));
                            }
                            break;
                        case "tan":
                            if (argumentContexts.Length == 1)
                            {
                                return new TanFunction((IValueExpression) VisitArgument(argumentContexts[0]));
                            }
                            break;
                        case "abs":
                            if (argumentContexts.Length == 1)
                            {
                                return new AbsFunction((IValueExpression) VisitArgument(argumentContexts[0]));
                            }
                            break;
                        case "sign":
                            if (argumentContexts.Length == 1)
                            {
                                return new SignFunction((IValueExpression) VisitArgument(argumentContexts[0]));
                            }
                            break;

                        case "to_number":
                        case "tonumber":
                            if (argumentContexts.Length == 2)
                            {
                                return new ToNumberFunction((IValueExpression) VisitArgument(argumentContexts[0]), (IConstantStringExpression)VisitArgument(argumentContexts[1]));
                            }
                            break;
                        case "lower":
                            if (argumentContexts.Length == 1)
                            {
                                var text = (IStringExpression) VisitArgument(argumentContexts[0]);
                                return new LowerFunction(text);
                            }
                            break;
                        case "upper":
                            if (argumentContexts.Length == 1)
                            {
                                var text = (IValueExpression) VisitArgument(argumentContexts[0]);
                                return new UpperFunction(text);
                            }
                            break;
                        case "replace":
                            if (argumentContexts.Length == 3)
                            {
                                var text = (IStringExpression) VisitArgument(argumentContexts[0]);
                                var oldValue = (IConstantStringExpression) VisitArgument(argumentContexts[1]);
                                var newValue = (IConstantStringExpression) VisitArgument(argumentContexts[2]);
                                return new ReplaceFunction(text, oldValue, newValue);
                            }
                            break;
                        
                        case "concat":
                            var items = argumentContexts.Select(o => (IStringExpression)VisitArgument(o)).ToList();
                            return new ConcatFunction(items);
                        case "reverse":
                            if (argumentContexts.Length == 1)
                            {
                                var text = (IStringExpression) VisitArgument(argumentContexts[0]);
                                return new ReverseFunction(text);
                            }
                            break;
                        case "round":
                            if (argumentContexts.Length == 2)
                            {
                                var number = (IValueExpression) VisitArgument(argumentContexts[0]);
                                var decimals = VisitArgument(argumentContexts[1]);
                                if (decimals is IUnaryExpression unaryExpression)
                                {
                                    return new RoundFunction(number, unaryExpression);
                                }

                                return new RoundFunction(number, (IIntegerExpression) decimals);

                            }
                            break;
                        case "variance":
                            if (argumentContexts.Length == 1)
                            {
                                var expr = (IValueExpression) VisitArgument(argumentContexts[0]);
                                return new VarianceFunction(expr);
                            }
                            break;
                    }
                }
            }

            return base.VisitGeneral_element_part(context);
        }

        public override ISqlObject VisitOther_function(NSqlParser.Other_functionContext context)
        {
            if (context.CAST() != null && context.AS() != null)
            {
                var concatenationContexts = context.concatenation();
                Debug.Assert(concatenationContexts.Length == 1);

                var exp = (IValueExpression)VisitConcatenation(concatenationContexts[0]);
                var targetType = context.type_spec()?.GetText();
                return new CastFunction(exp, targetType);
            }

            if (context.EXTRACT() != null && context.FROM() != null)
            {
                var concatenationContexts = context.concatenation();
                if (concatenationContexts.Length == 1)
                {
                    var dateTime = (IValueExpression) VisitConcatenation(concatenationContexts[0]);
                    var t =context.regular_id().GetText().ToLower();
                    switch (t)
                    {
                        case "year":
                            return new ExtractFunction(dateTime, ExtractType.Year);
                        case "month":
                            return new ExtractFunction(dateTime, ExtractType.Month);
                        case "week":
                            return new ExtractFunction(dateTime, ExtractType.Week);
                        case "day":
                            return new ExtractFunction(dateTime, ExtractType.Day);
                        case "hour":
                            return new ExtractFunction(dateTime, ExtractType.Hour);
                        case "minute":
                            return new ExtractFunction(dateTime, ExtractType.Minute);
                        case "second":
                            return new ExtractFunction(dateTime, ExtractType.Second);
                    }
                }
            }


            var overClauseKeyword = context.over_clause_keyword();
            if (overClauseKeyword != null)
            {
                if (overClauseKeyword.STDDEV() != null)
                {
                    var argument = (IValueExpression)VisitArgument(context.function_argument_analytic().argument()[0]);
                    return new StdDevFunction(argument);
                }

                if (overClauseKeyword.VARIANCE() != null)
                {
                    var argument = (IValueExpression)VisitArgument(context.function_argument_analytic().argument()[0]);
                    return new VarianceFunction(argument);
                }

            }
            return base.VisitOther_function(context);
        }

        public override ISqlObject VisitQuantified_expression(NSqlParser.Quantified_expressionContext context)
        {
            if (context.EXISTS() != null)
            {
                return new ExistsExpression((IDataSetStatement) VisitSubquery(context.subquery()));
            }
            return base.VisitQuantified_expression(context);
        }

        public override ISqlObject VisitSubquery(NSqlParser.SubqueryContext context)
        {
            var subQuery = VisitSubquery_basic_elements(context.subquery_basic_elements());
            var operationPart = context.subquery_operation_part();
            if (operationPart?.Length > 0)
            {
                var a = (IDataSetStatement) subQuery;

                foreach (var operationPartContext in operationPart)
                {
                    var otherSubQuery = (IDataSetStatement)VisitSubquery_basic_elements(operationPartContext.subquery_basic_elements());
                    if (operationPartContext.UNION() != null)
                    {
                        a = new UnionStatement(a,  otherSubQuery,
                            operationPartContext.ALL() != null ? UnionType.All : UnionType.Distinct);
                    }

                    if (operationPartContext.MINUS() != null || operationPartContext.EXCEPT() != null)
                    {
                        a = new MinusStatement(a, otherSubQuery);
                    }
                    
                    if (operationPartContext.INTERSECT() != null)
                    {
                        a = new IntersectionStatement(a, otherSubQuery);
                    }
                }

                return a;
            }
            return subQuery;
        }

        public override ISqlObject VisitExtract_function(NSqlParser.Extract_functionContext context)
        {
            var time = (IDateTimeExpression) VisitConcatenation(context.concatenation());
            if (context.YEAR() != null)
            {
                return new ExtractFunction(time, ExtractType.Year);
            }
            if (context.MONTH() != null)
            {
                return new ExtractFunction(time, ExtractType.Month);
            }
            if (context.WEEK() != null)
            {
                return new ExtractFunction(time, ExtractType.Week);
            }
            if (context.DAY() != null)
            {
                return new ExtractFunction(time, ExtractType.Day);
            }
            if (context.HOUR() != null)
            {
                return new ExtractFunction(time, ExtractType.Hour);
            }
            if (context.MINUTE() != null)
            {
                return new ExtractFunction(time, ExtractType.Minute);
            }
            if (context.SECOND() != null)
            {
                return new ExtractFunction(time, ExtractType.Second);
            }
            return base.VisitExtract_function(context);
        }
    }
}